/***************************************************************************
 *   Copyright (C) 2008 by Jesse Crossen   *
 *   jesse.crossen@gmail.com   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "strobe.h"

Strobe::Strobe(QWidget *parent)
 : QWidget(parent)
{
  // set defaults
  noteFrequency = 440.0;
  noteName = "A4";
  spinPhase = 0.0;
  contrast = 0.75;

  // always stay large enough to read the display
  setMinimumSize(64, 64);

  // zero all segments
  for(int i = 0; i < SEGMENTS; i++) {
    Segments[i] = 0.5;
  }

}

Strobe::~Strobe()
{
}

const QSize Strobe::sizeHint() {
  return(QSize(100, 100));
}

// draw the strobe
void Strobe::paintEvent(QPaintEvent * /* event */)
{
  int s;
  QPainter painter(this);
  
  // draw with full alpha
  int alpha = 255;
  // use smooth rendering if possible
  painter.setRenderHint(QPainter::Antialiasing);
  // set the origin to the control's center
  painter.translate(rect().width() / 2, rect().height() / 2);
  // size everything to the smaller dimension
  int radius = rect().width();
  if (rect().height() < radius) radius = rect().height();
  radius = (radius / 2) - 2;
  // set up a pen to draw between segments
  QPen pen(QColor(0, 0, 0, alpha), 1);
  // establish the outer and inner bounds of the segments
  int innerRadius = radius / 2;
  int outerRadius = radius;
  QRect outer(-outerRadius, -outerRadius, outerRadius * 2, outerRadius * 2);
  QRect inner(-innerRadius, -innerRadius, innerRadius * 2, innerRadius * 2);
  // draw the segments
  painter.save();
    // do the trig to draw the segment
    float angle = (2.0 * 3.141592652589793) / (float)SEGMENTS;
    float dx = cos(angle);
    float dy = sin(angle);
    // build a polygon for the segment
    QPointF segmentOutline[4] = {
     QPointF((float)innerRadius * dx, (float)innerRadius * dy),
     QPointF((float)innerRadius, 0.0),
     QPointF((float)outerRadius, 0.0),
     QPointF((float)outerRadius * dx, (float)outerRadius * dy)
    };
    // compute the step for each segment
    qreal angleStep = 360.0 / (qreal)SEGMENTS;
    int color = 0;
    // step through segments (in reverse so sharp rotates right)
    for (s = SEGMENTS - 1; s >= 0; s--) {
      // fill segment
      color = (int)floor(Segments[s] * 255);
      painter.setBrush(QColor(color, color, color, alpha));
      painter.setPen(Qt::NoPen);
      // draw the segment
      painter.drawConvexPolygon(segmentOutline, 4);
      // divide segments
      painter.setPen(pen);
      painter.drawLine(inner.right(), 0, outer.right(), 0);
      // move to next location
      painter.rotate(angleStep);
    }
  painter.restore();
  // outline the segments
  pen.setWidth(2);
  painter.setPen(pen);
  painter.drawEllipse(outer);
  painter.drawEllipse(inner);
  
  // draw the current note name
  QFont f(painter.fontInfo().family());
  f.setPixelSize((innerRadius * 3) / 4);
  f.setBold(true);
  painter.setFont(f);
  QRect textRect(-innerRadius, -innerRadius, innerRadius * 2, innerRadius * 2);
  painter.drawText(textRect, Qt::AlignCenter, noteName);
  
}

// set the frequency we are tuning to
void Strobe::setNoteFrequency(float frequency) {
  if (noteFrequency != frequency) {
    noteFrequency = frequency;
    update();
  }
}
// set the name of the note we are tuning to
void Strobe::setNoteName(QString name) {
  if (noteName != name) {
    noteName = name;
    update();
  }
}
// set the display contrast
void Strobe::setContrast(float newContrast) {
  if (newContrast < 0.0) newContrast = 0.0;
  if (newContrast > 1.0) newContrast = 1.0;
  if (contrast != newContrast) {
    contrast = newContrast;
    // there's no need for an update, since this affects 
    //  how audio is processed and will be displayed
    //  when the next buffer arrives
  }
}

// process audio input
void Strobe::addAudio(const float *buffer, unsigned long framesPerBuffer,
                      float sampleRate, float peak)
{
  unsigned long i;
  const float *in;
  // if the control is not visible, there's no point to updating it
  if (! isVisible()) return;
  // keep a count of how many times each segment is lit and not lit
  static int dark[SEGMENTS];
  static int light[SEGMENTS];
  for(i = 0; i < SEGMENTS; i++) {
    dark[i] = 0;
    light[i] = 0;
  }
  // set a threshold value based on the signal peak
  float threshold = peak * contrast;
  // calculate the amount to change the spin phase each sample
  //  so that it makes one revolution per period
  float phaseStep = 1.0 / ((float)sampleRate / noteFrequency);
  // add samples to the segment
  //TODO: should we limit the number of cycles added so that high frequencies don't blur so much?
  in = buffer;
  int pos;
  for(i = 0; i < framesPerBuffer; i++) {
    // find which segment we're adding samples to
    pos = (int)floor(spinPhase * (float)SEGMENTS);
    // add the sample to the current segment
    if (*in++ > threshold) light[pos]++;
    else dark[pos]++;
    // spin the "wheel"
    spinPhase += phaseStep;
    if (spinPhase >= 1.0) spinPhase -= 1.0;
  }
  
  for(i = 0; i < SEGMENTS; i++) {
    Segments[i] = ((float)light[i] / (float)(light[i] + dark[i]));
  }
  
  // redraw the widget next chance we get to show changes
  update();
}
